﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Dynamic;
using System.ComponentModel;
using System.Windows.Threading;

namespace FinPlusCommon
{
    public sealed class FuncDynamic : DynamicObject, INotifyPropertyChanged, IFinPlusFuncDynamic
    {
        public event PropertyChangedEventHandler PropertyChanged, PropertyColumn;
        public string ServiceKey;
        public string User;

        private enum Params { Id, FunctionName };
        private ConcurrentDictionary<string, object> _values = new ConcurrentDictionary<string, object>();
        private ConcurrentDictionary<string, DateTime> _timeStamps = new ConcurrentDictionary<string, DateTime>();
        private ConcurrentDictionary<string, string> _users = new ConcurrentDictionary<string, string>();
        private SortedDictionary<int, string> _names = new SortedDictionary<int, string>();

        //construct
        public FuncDynamic(IEnumerable<IFinPlusItem> func)
        {
            foreach (var item in func)
                SetItem(item);
        }

        //override
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            return _values.TryGetValue(binder.Name, out result);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            _values[binder.Name] = value;
            _timeStamps[binder.Name] = DateTime.Now;
            _users[binder.Name] = User;
            NotifyFunctionChanged(binder.Name);
            return true;
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            if (_names != null)
                return _names.Values;
            return _values.Keys;
        }

        //public
        public IFinPlusFunction ToFunction()
        {
            object id = -1;
            _values.TryGetValue(Params.Id.ToString(), out id);
            var func = new Function() { Id = (int)id };

            foreach (var name in _names)
                func.Add(name.Value, name.Key, (int)id, _values[name.Value], _users[name.Value], _timeStamps[name.Value]);
            
            return func;
        }

        public void SetValue(string name, object value, string user, DateTime timeStamp)
        {
            _values[name] = value;
            _timeStamps[name] = timeStamp;
            _users[name] = user;

            NotifyFunctionChanged(name);
        }

        public void SetItem(IFinPlusItem item)
        {
            SetValue(item.Name, item.Type.Equals("Function") ? item.Value : item.Object, item.User, item.TimeStamp);
            AddColumn(item);
        }

        public void AddColumn(IFinPlusItem item)
        {
            if (_names.ContainsValue(item.Name)) return;

            _names[item.Index] = item.Name;
            NotifyPropertyColumn(item.Name);
        }

        public object GetValue(string name)
        {
            if (_values.ContainsKey(name))
                return _values[name];
            return string.Empty;
        }

        public DateTime GetTimeStamp(string name)
        {
            return _timeStamps.ContainsKey(name) ? _timeStamps[name] : DateTime.MinValue;
        }

        public string GetType(string name)
        {
            if (_values.ContainsKey(name))
                return _values[name].GetType().Name;
            return null;
        }

        public bool TryGetObject(string name, out object value)
        {
            if (_values.ContainsKey(name))
            {
                value = _values[name];
                return true;
            }
            value = string.Empty;
            return false;
        }

        public bool HasObject(string name, object value)
        {
            if (_values.ContainsKey(name) && _values[name].Equals(value))
                return true;
            return false;
        }

        public bool ContainsObject(string name, object value)
        {
            if (_values.ContainsKey(name) && _values[name].ToString().Split(',').Contains(value.ToString()))
                return true;
            return false;
        }

        public IDictionary<DateTime, object> GetDateSeries()
        {
            return _values.Where(x => x.Value.GetType() == typeof(double)).ToDictionary(x => DateTime.Parse(x.Key), x => x.Value);
        }

        public new string ToString()
        {
            var s = new StringBuilder();
            foreach (var kvp in _values)
            {
                s.Append(kvp.Key.ToString());
                s.Append(',');
            }
            s.Append(';');
            foreach (var kvp in _values)
            {
                s.Append(kvp.Value.ToString());
                s.Append(',');
            }
            return s.ToString();
        }

        //private
        private void NotifyFunctionChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
        }

        private void NotifyPropertyColumn(string name)
        {
            if (PropertyColumn != null)
                PropertyColumn.Invoke(this, new PropertyChangedEventArgs(name));
        }

        //event
        private void Item_Updated(object sender, ItemEventArgs a)
        {
            a.Item.User = User;
            SetItem(a.Item);
        }
    }
}
